<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=0">
  <meta http-equiv="Expires" content="0">
  <meta http-equiv="Pragma" content="no-cache">
  <meta http-equiv="Cache-control" content="no-cache">
  <meta http-equiv="Cache" content="no-cache">
  <link rel="icon" href="data:;base64,=">
  <title>无意义</title>
  </head>
  <style>
  *{margin:0 auto;padding:0;box-sizing:border-box;}
  html,body{position:absolute;width:100%;height:100%;min-width:1366px;min-height:768px;background: linear-gradient(skyblue,khaki);}
  /* 定义滚动条宽度 */::-webkit-scrollbar {width:4px;height:4px;}
  /*定义滚动条  轨道背景色+圆角*/::-webkit-scrollbar-track {border-radius: 2px;background-color: #555;}
  /*定义滚动条  滑块背景色+圆角*/::-webkit-scrollbar-thumb {border-radius: 2px;background-color: #C3C4C3;}
  /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
  
  </style>
<body>

   <div id="dom">
        \" 
      </div>
   <input type='radio' name="a" class="radio" value="a">
   <input type='radio' name="a" class="radio" value="b">
   <input type='radio' name="a" class="radio" value="c">
   <input type='radio' name="a" class="radio" value="d">
</body>
</html>
<script>
var dom=document.getElementById('dom');

var getFork=function(){
      return {}
};
var BD0=Object.create(null);
    BD0['{']=1;
    BD0['}']=2;
    
/**
 * @param {*}   str =   " abc {{xx}}  "
 *                  ↓↓↓↓↓
 * @param {*}   arr = [' abc ', '{{xx}} ']
 * 
 */
var BraceContentToFunctin=function(str){
        str=str.trim();
    var i=0,l=str.length,s='',u;
    var a=[],al=0,n;
    var k=[],ds=0,d=[],c;
    var p=0,q=0,o=0,vl=0,f=0; 
    var strf='';
        /* 
          将 str 中的 {{ }} 内的 部分提取出来  
        */
        while(i<l){
           s=str[i];
           n=BD0[s];
           if(n!==u){
            //    if(al===0&&n===1)f=true;   //新一轮括号的开始 
               if(f===1&&n===2){
                    a.pop();
                    al--;
                    // al=0  说明 一个完整闭合的结束 
                    if(al===0){
                          f=0;
                          k.push(ds,i+1);
                          ds=i+1; 
                    };
               }else if(n===1){
                    a.push(n);        
                    al++;
                    if(al===2){ //两个{{ 开头
                        if(a[0]===a[1]){
                            ds=i-1;
                            f=1;
                        }else{
                            a.length=0;
                            al=0;
                        };
                    };
               }
             
           };
          i++;
        };
        // 判断漏掉的部分是不是 非字符串部分  (上面的方法,会漏掉结尾的部分)
        // if(ds<l)k.push([ds,l]);  
        console.log(k);
        i=0,al=k.length;

        while(i<al){
            p=k[i],q=k[i+1];
            if(p>o){
                strf+='"'+str.slice(o,p)+'"+';
            };
            strf+='('+parser(str.slice(p+2,q-2))+')';
            //尾部之前才有+
            if(al-i>2)strf+='+';
            o=q;
            i+=2;
        };
        return strf;
};
/**
 * 语法解释器
 * 提取出所有参数 
 */
var BD1=Object.create(null);
var BD2=Object.create(null);
    // 用于匹配字符串的边界 
    BD1["'"]=1,BD1['"']=2,BD1['`']=3;  
    // 用于匹配三目和各种运算符的边界
    BD2['+']=1,BD2['-']=1,BD2['*']=1,BD2['\\']=1,BD2['%']=1,BD2['?']=1,BD2['>']=1,BD2['<']=1,BD2['=']=1,BD2['&']=1,BD2[':']=1,BD2[',']=1;
    BD2['(']=2,BD2[')']=1;     //2 方法 () 
    BD2['[']=3,BD2[']']=4;     //3  data[  ]的方法
    
   
    // BD2['']=true;
    // BD2['']=true;

/**
 *   例: str= ' abc + "haha" + fun() + aaa. '
 * 
 * 
 *   return  function string
 */    
var parser = function( str ){
        str=str.trim();
    var i=0,l=str.length,s='',u;
    var a=[],al=0,n;
    var k=[],ds=0,d=[],c;
    var p=0,q=0,o=0,vl=0; 
        /* 
          将 str 中的 非 string 部分提取出来  
        */
        while(i<l){
           s=str[i];
           n=BD1[s];
           if(n!==u){
               if(a[al-1]===n){ 
                    // 相同的字符串边界的长度 相减
                    a.pop();
                    al--;
                    // al=0  说明 一个闭合的string 结束 
                    if(al===0)ds=i+1;   
               }else{
                    // 不同的字符串边界的长度 相加
                    a.push(n);        
                    al++;
                    // 字符串边界长度==1时, 且ds!==i 说明  ds到 i 的索引部分是非字符串部分
                    if(al===1&&ds!==i)k.push(ds,i);
               };
           };
          i++;
        };
        // 判断漏掉的部分是不是 非字符串部分  (上面的方法,会漏掉结尾的部分)
        if(ds<l)k.push(ds,l);  
        console.log(k);
        //提取出非字符串的部分并合并
        i=0,al=k.length;
      
     var str2='';   
        while(i<al){
           p=k[i],q=k[i+1];
           // string 部分
           if(p>o){
               s=str.slice(o,p).trim();
               d.push(s);
               str2+='$♋$';
           };
           // key部分
           s=str.slice(p,q).trim();
           str2+=s;
           o=q;
           i+=2;
        }; 
        str2= parser2(str2,d);
        //尾部漏掉的 "xxx"
        if(q<l)str2+=str.slice(q,l)
      return str2;

};



/**
 *  提取参数和方法 
 * 
 */ 
var parser2=function(str,strD){
    var i=0,l=str.length,s='',u;
    var n=0,ns=0,pn=0;
    var d=[],dl=-1,ds='',dd,pd;
    var f=0;    // f=> 1 : 之前是个key  0:之前是个运算符   f2===2 说明它们之间隔着一个 key
        console.log(str);
        while(i<l){
           s=str[i];
           n=BD2[s]; 
        //    if(s===']')debugger; 
           //匹配到一个运算符  
           if(n!==u){
               f=0;
               if(ns!==i){
                   //提取非运算符之前的 部分
                   ds=str.slice(ns,i).trim();
                   if(ds!==''){
                        //说明 i 之前的部分可能是合法的 key
                        dl++;
                        d.push([ds, 0, ns,i ])       //  *1=> 0:普通key  1:数字 , 2: 方法名  3: data[key]  *2~*3: 值的提取部分 
                        if(ds!=='$♋$'){
                            pd=d[dl];
                        };
                        f=1;
                   };
               };
               if(n===2){ //    方法()
                      if(pd!==u){
                          if(f===1||pn===4){
                                pd[1]=2;    // xxxx() || ['xxx']()
                          };
                      };  
               };
               if(n===3){ // [   data[key] 的方式 
                   if(f===1){
                        if(pd!==u)pd[1]=3;
                   }else{
                        d.push(['$fork',3, i,i ]) 
                        dl++
                   };
               };
               pn=n;
               ns=i+1;
           };
          i++;
        };
        if(ns<l){
              ds=str.slice(ns,l).trim();
              if(ds!==''){
                 d.push([ds, 0, ns, l])
                 dl++;
              };
        };
        i=0,dl=d.length;
        if(dl===0)return;
        console.log(d);
       var dkr=[],fork={} 
       var kstr='',kn=0,ks=0,ns=0,nt=0,pt=0,kk='',k0='';
        while(i<dl){
            dd=d[i];
            s=dd[0];
            ks=dd[2];  // 该key 的索引开始
            nt=dd[1];  // 当前类型 
            if(ks>ns){
                kk=str.slice(ns,ks).trim();
                // kstr+=kk;;
                if(pt!==2){
                     kstr+=kk;
                }else{
                    k0=kk[0];
                    if(k0==='('&&kk.length!==1){
                         pt=0
                         kstr+='($evt,$dom'+kk.slice(1).trim();
                    }else if(k0===')'){
                         pt=0;
                         kstr+=',$evt,$dom)'+kk.slice(1).trim();
                    }else{
                         kstr+=kk;
                    };
                };
            };
            if(s==='$♋$'){
                kstr+=strD[kn];
                kn++;
            }else{
                s=parserKey(s,nt,fork,dkr);
                kstr+=s;
            };
            if(nt===2)pt=nt;
            ns=dd[3]; // 该key 的索引结束
          i++;
        };
        if(l>ns){
            kk=str.slice(ns,l).trim();
            if(pt===2){
                 if(kk===')'){
                    kk=',$evt,$dom)'
                 }else{
                    kk='($evt,$dom)'
                 };
            };
            kstr+=kk;
        };   
        
      return kstr;

        
};


var regKh=/\{\{((?:.|\n)+?)\}\}/g;
var regFun=/^([A-z]|\_|\$)+\d*\s*\(.*\)$/;          // 以 字母_或$ 开头的 方法   
var regCs=/^([A-z]|\_|\$)+/;                        // 以字母 _ 和 $ 开头的 肯定是 参数名  (  排除掉 '"abc"'  这种 )


var KT={
    '$_index':'$O.$N(1)',
    '$_value':'$O.$N(0)',
    '$fork':'$data'
}
/**
 * 对parser2 的数据在此解析  
 * 
*/
var parserKey=function(key,type,fork, dkr){
    var d,u;
        if(type===2){
            return '$methods.'+key;
        };   
        if(regCs.test(key)){
            if(key.indexOf('.')<0){ //单值
                    d=fork[key];
                    if(d===u){
                        d=[],fork[key]=d;
                        dkr.push(d);
                    }else if(dkr.indexOf(d)<0){ // 每个dom 重复key应该处理一次 
                        dkr.push(d);
                    };
                return KT[key]||'$data.'+key;
            }else{//路径                 
                    var krr=key.split('.'),k='',i=0,l=krr.length,e=l-1,o=fork,c;
                        while(i<l){
                            k=krr[i].trim();
                            if(i!==e){ 
                                // 非最后一个值前面都是路径
                                d=o[k];
                                if(d===u){
                                    d=getFork();
                                    d.$p=o;
                                    o[k]=d;
                                };
                                o=d;
                        };
                        if(i===0)krr[i]='$O.'+k;
                        if(i===e){
                            krr[i]=KT[k]||'$data.'+k;
                                c=o[k];
                                if(c===u){
                                    c=[],o[k]=c;
                                    dkr.push(c)
                                }if(dkr.indexOf(c)<0){
                                    dkr.push(c)
                                };
                        }; 
                        i++;
                        };
                        return krr.join('.')
            };
        }else{
               return key;  //数值 或 非法的key
        };     
};

var str='fun1( ) + abc + fun2( ) + fun(abc+ade) + test(   ) ';
// var str=' abc.fun["getNumber"]() ';
var start=new Date().getTime();


  console.log( parser (str))


console.log(new Date().getTime() - start  );


// var str2=` 测试 {{ a+b+c+'{{}}' }}  行不行  {{ c+d+e}} ___ {{ abc + 123 +  "hehe"  + fun( )  + ( a>b?(a<c?"hehe":"haha" + ["haha"]):'null')  +  abc.txt.haha + abc.fun["getNumber"]()}}   `;

// var str2=`{{ test(txt0, txt1 , base.haha ) }} `;
// debugger
// var ss= BraceContentToFunctin(str2)
// console.log(ss)

var regWrap=/(\r|\n)/g;
var getVfun=function(str){
          
    return Function('"use strict";return (' + str.replace(regWrap,'') + ')')() 
};

var fun=function(str){
   return getVfun('function($O,$methods){ var $data=$O.$data;  return '+str+'}');
};

// console.log( parser (str))
// console.log( parser (" a+b+c+'{{}}' "))
</script>